#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <semaphore.h>
#include <ctime>

using namespace std;

template <class T>
class RingQueue
{
private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);
    }
    void V(sem_t &sem)
    {
        sem_post(&sem);
    }
    void Lock(pthread_mutex_t &mutex)
    {
        pthread_mutex_lock(&mutex);
    }
    void Unlock(pthread_mutex_t &mutex)
    {
        pthread_mutex_unlock(&mutex);
    }

public:
    RingQueue(int capacity = 5)
        : _ringqueue(capacity), _capacity(capacity), _cstep(0), _pstep(0)
    {
        sem_init(&_cdatasem, 0, 0);         // 消费者
        sem_init(&_pspacesem, 0, capacity); // 生产者

        pthread_mutex_init(&_cmutex, nullptr);
        pthread_mutex_init(&_pmutex, nullptr);
    }
    void push(const T &in) // 生产
    {
        // 多个线程来申请信号量,一定会出现都申请信号量成功的现象
        // 此时对这个环形队列的下标展开了竞争
        // 从而就会出现值覆盖的情况
        // 多生产者和多消费的场景在任一时刻,依然只允许一个生产者一个消费者进入队列
        // 消费者和消费者之间对下标访问需要有一把锁：_cmutex;
        // 生产者和生产者之间对下标访问需要有一把锁：_pmutex;

        // 加锁位置应该放在那里呢？
        // 1.首先我们要知道信号量资源是原子的,它不需要加锁的,不需要被保护
        // 之前我们也曾提到临界区的代码量一定要少
        // 所以一般而言我们不需要加锁信号量

        // 2.如果先申请锁,再去申请信号量,这样他们之间一定是串行的
        // 但是把申请信号量放在加锁之前,一个线程释放锁的时候,其他线程早已经申请到了信号量
        // 此时只用去申请锁,一定程度上做到了申请信号量和申请锁的并行

        P(_pspacesem); // 申请空间资源,申请成功空间少一个
        Lock(_pmutex);
        // 生产
        _ringqueue[_pstep] = in;
        // 更新生产者下标,维持环形特征
        _pstep++;
        _pstep %= _capacity;
        Unlock(_pmutex);
        V(_cdatasem); // 得到数据资源,得到成功数据多一个
    }
    void pop(T *out) // 消费
    {
        P(_cdatasem); // 申请数据资源,申请成功数据少一个
        Lock(_cmutex);
        // 消费
        *out = _ringqueue[_cstep];
        // 更新消费者下标,维持环形特征
        _cstep++;
        _cstep %= _capacity;
        Unlock(_cmutex);
        V(_pspacesem); // 释放空间资源,释放成功空间多一个
    }
    ~RingQueue()
    {
        sem_destroy(&_cdatasem);
        sem_destroy(&_pspacesem);

        pthread_mutex_destroy(&_cmutex);
        pthread_mutex_destroy(&_pmutex);
    }

private:
    vector<T> _ringqueue;
    int _capacity;

    int _cstep; // 消费者下标
    int _pstep; // 生产者下标

    // 信号量
    sem_t _cdatasem;  // 消费者信号 -> 关注数据资源
    sem_t _pspacesem; // 生产者信号 -> 关注空间资源

    pthread_mutex_t _cmutex;
    pthread_mutex_t _pmutex;
};